﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.LowLevel;

public static partial class LoopRunner
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
    public static void Init()
    {
        //UnityEngine.Debug.Log($"LoopRunner Init");
        var playerLoop = PlayerLoop.GetCurrentPlayerLoop();
        var upType = typeof(UnityEngine.PlayerLoop.Update);
        var lateUpType = typeof(UnityEngine.PlayerLoop.PostLateUpdate);
        for (int i = 0; i < playerLoop.subSystemList.Length; i++)
        {
            var subSys = playerLoop.subSystemList[i];
            if (subSys.type == upType || subSys.type == lateUpType)
            {
                var len = subSys.subSystemList.Length;
                var list = new PlayerLoopSystem[len + 1];
                Array.Copy(subSys.subSystemList, list, len);
                list[len] = new PlayerLoopSystem() {
                    type = subSys.type,
                    updateDelegate = subSys.type == upType ? Update : LateUpdate,
                };
                subSys.subSystemList = list;
                playerLoop.subSystemList[i] = subSys;
            }
        }
        PlayerLoop.SetPlayerLoop(playerLoop);
    }

    private static LinkedList<UnityAwaiter> upFuncs = new LinkedList<UnityAwaiter>();
    private static LinkedList<UnityAwaiter> lateUpFuncs = new LinkedList<UnityAwaiter>();

    public static int AsyncNum = 5;

    private static void RunAwaiter(LinkedList<UnityAwaiter> list, int count)
    {
        var node = list.First;
        while (node != null && count-- > 0)
        {
            var awaiter = node.Value;
            if (!awaiter.MoveNext())
            {
                var rm = node;
                list.Remove(rm);
            }
            node = node.Next;
        }
    }

    private static void Update()
    {
        RunAwaiter(upFuncs, AsyncNum);
    }
    private static void LateUpdate()
    {
        RunAwaiter(lateUpFuncs, AsyncNum);
    }

    private static void Add(UnityAwaiter awaiter, bool isLate = false)
    {
        var list = isLate ? lateUpFuncs : upFuncs;
        list.AddLast(awaiter);
    }

    public static async Task WaitUntil(Func<bool> func, bool isLate = false)
    {
        var wait = new WaitUntilAwaiter(func);
        Add(wait, isLate);
        await wait;
    }

    public static async Task Wait(IEnumerator itor, float duration = 0.033f, bool isLate = false)
    {
        var wait = new WaitForCoroutine(itor);
        wait.duration = duration;
        Add(wait, isLate);
        await wait;
    }

    public static async Task RunForView(IEnumerator itor, int frameRunNum, bool isLate = false)
    {
        var wait = new WaitForCoroutine(itor);
        wait.frameRunNum = frameRunNum;
        Add(wait, isLate);
        await wait;
    }

    public static async Task WaitFrame(int frameNum, bool isLate = false)
    {
        var wait = new WaitDelayFrameAwaiter(frameNum);
        Add(wait, isLate);
        await wait;
    }

    public static async Task Delay(float delay, bool isLate = false)
    {
        var wait = new DelayAwaiter(delay);
        Add(wait, isLate);
        await wait;
    }

}
